﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Celeriq.Common;
using Celeriq.Utilities;
using Celeriq.DataCore.EFDAL;
using Celeriq.DataCore.EFDAL.Entity;
using Celeriq.DataCore.EFDAL.Interfaces;

namespace Celeriq.Server.Core
{
    internal static class StatLogger
    {
        private const int LOG_TIMER_INTERVAL = 30 * 1000;
        private static bool _ready = false;
        private static Dictionary<Guid, List<RepositorySummmaryStats>> _statCache = new Dictionary<Guid, List<RepositorySummmaryStats>>();
        private static System.Timers.Timer _timer = null;

        private static string ConnectionString
        {
            get
            {
                var fi = new FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location);
                var filename = Path.Combine(fi.DirectoryName, "stats.db4");
                return @"Data Source=" + filename;
            }
        }

        #region Init

        public static void Initialize()
        {
            try
            {
                //Log repository stats every N seconds
                _timer = new System.Timers.Timer(LOG_TIMER_INTERVAL);
                _timer.AutoReset = false;
                _timer.Elapsed += TimerTick;
                _timer.Start();

                _ready = true;
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
            }
        }

        public static void Shutdown()
        {
            try
            {
                _timer.Stop();
                TimerTick(null, null);
            }
            catch (Exception ex)
            {
                throw;
            }
        }

        #endregion

        #region Server Stats

        public static void Log(RealtimeStats item)
        {
            if (!_ready) return;

            try
            {
                using (var context = new DataCoreEntities())
                {
                    var newItem = new ServerStat()
                                  {
                                      MemoryUsageTotal = item.MemoryUsageTotal,
                                      MemoryUsageAvailable = item.MemoryUsageAvailable,
                                      MemoryUsageProcess = item.MemoryUsageProcess,
                                      RepositoryInMem = item.RepositoryInMem,
                                      RepositoryLoadDelta = item.RepositoryLoadDelta,
                                      RepositoryUnloadDelta = item.RepositoryUnloadDelta,
                                      RepositoryTotal = item.RepositoryTotal,
                                      RepositoryCreateDelta = item.RepositoryCreateDelta,
                                      RepositoryDeleteDelta = item.RepositoryDeleteDelta,
                                      ProcessorUsage = item.ProcessorUsage,
                                  };
                    context.AddItem(newItem);
                    context.SaveChanges();
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
            }
        }

        public static List<RealtimeStats> QueryServerStats(DateTime start, DateTime end)
        {
            if (!_ready) return null;

            try
            {
                var retval = new List<RealtimeStats>();
                using (var context = new DataCoreEntities())
                {
                    var list = context.ServerStat.Where(x => x.AddedDate <= start && x.AddedDate < end).OrderBy(x => x.AddedDate);
                    foreach (var item in list)
                    {
                        retval.Add(new RealtimeStats
                                   {
                                       Timestamp = item.AddedDate,
                                       MemoryUsageAvailable = item.MemoryUsageAvailable,
                                       MemoryUsageProcess = item.MemoryUsageProcess,
                                       MemoryUsageTotal = item.MemoryUsageTotal,
                                       ProcessorUsage = item.ProcessorUsage,
                                       RepositoryCreateDelta = (int)item.RepositoryCreateDelta,
                                       RepositoryDeleteDelta = (int)item.RepositoryDeleteDelta,
                                       RepositoryInMem = item.RepositoryInMem,
                                       RepositoryLoadDelta = item.RepositoryLoadDelta,
                                       RepositoryTotal = item.RepositoryTotal,
                                       RepositoryUnloadDelta = item.RepositoryUnloadDelta,
                                   });
                    }
                }
                return retval;
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                return null;
            }
        }

        #endregion

        #region Repository Stats

        private static void TimerTick(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (!_ready) return;

            _timer.Stop();
            try
            {
                //Lock the stats list and build queries
                Dictionary<Guid, List<RepositorySummmaryStats>> copyCache = null;
                lock (_statCache)
                {
                    copyCache = _statCache.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
                    _statCache.Clear();
                }

                using (var context = new DataCoreEntities())
                {
                    var typeValues = Enum.GetValues(typeof(RepositoryActionTypeConstants)).Cast<int>().ToList();
                    foreach (var t in typeValues)
                    {
                        foreach (var key in copyCache.Keys)
                        {
                            var q = (RepositoryActionConstants)t;
                            var queryList = copyCache[key].Where(x => x.ActionType == q).ToList();
                            var elapsed = queryList.Sum(x => x.Elapsed); //Total elapsed time
                            var count = queryList.Count; //Number of queries
                            var itemCount = 0;
                            if (queryList.Count > 0)
                                itemCount = queryList.Sum(x => x.ItemCount);

                            //Ensure repository still exists (may have been removed in interim)
                            var repository = context.RepositoryDefinition.FirstOrDefault(x => x.UniqueKey == key);
                            if (repository != null && (count > 0 || elapsed > 0 || itemCount > 0))
                            {
                                    var newItem = new RepositoryStat()
                                                  {
                                                      Count = count,
                                                      Elapsed = elapsed,
                                                      ItemCount = itemCount,
                                                      RepositoryActionTypeId = int.Parse(t.ToString("d")),
                                                      RepositoryId = repository.RepositoryId,
                                                  };
                                    context.AddItem(newItem);
                            }
                        }
                    }
                    context.SaveChanges();
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
            }
            finally
            {
                _timer.Start();
            }
        }

        public static void Log(RepositorySummmaryStats item)
        {
            try
            {
                lock (_statCache)
                {
                    if (!_statCache.ContainsKey(item.RepositoryId))
                        _statCache.Add(item.RepositoryId, new List<RepositorySummmaryStats>());
                    _statCache[item.RepositoryId].Add(item);
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
            }
        }

        public static RepositorySummmaryStats QueryRepositoryStats(Guid repositoryId, DateTime start, DateTime end)
        {
            if (!_ready) return null;

            try
            {
                var retval = new RepositorySummmaryStats() { ActionType = RepositoryActionConstants.Query };
                using (var context = new DataCoreEntities())
                {
                    var repository = context.RepositoryDefinition.FirstOrDefault(x=>x.UniqueKey == repositoryId);
                    if (repository == null)
                        throw new Exception("Unknown Repository");

                    var actionId = (int)RepositoryActionConstants.Query;
                    var lambda = context.RepositoryStat.Where(x => start <= x.CreatedDate &&
                        x.CreatedDate < end &&
                        (repositoryId == Guid.Empty || repository.RepositoryId == x.RepositoryId) &&
                        x.RepositoryActionTypeId == actionId)
                        .OrderBy(x => x.CreatedDate);

                    long count = 0;
                    long totalElapsed = 0;
                    if (lambda.Any())
                    {
                        count = lambda.Sum(x => x.Count);
                        totalElapsed = lambda.Sum(x => x.Elapsed);
                    }

                    var elapsedPer = 0.0;
                    if (count > 0) elapsedPer = ((totalElapsed * 1.0) / count);

                    retval.ItemCount = (int)count;
                    retval.Elapsed = (int)elapsedPer;
                    retval.RepositoryId = repositoryId;
                }
                return retval;
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                return null;
            }
        }

        #endregion
    }
}
